You have implemented Microsoft Authentication Library or Microsoft Identity Web and now you are seeing the following error message:
No account or login hint was passed to the AcquireTokenSilent
The root cause is because the Token Cache is empty when you are trying to acquire a token silently when account was attempted to be pulled from MSAL.
So on Web Applications like Asp.Net or Asp.Net Core, this is generally when the user is still passing a authentication cookie to the web app but the web app has restarted or their is high memory usage where the memory gets cleared out therefore the MSAL token cache is empty. Also keep in mind, MSAL does have a default cache size where MSAL will start clearing old entries from its cache.
There are a few solutions to this. We would recommend implementing persistent token cache…
Either implement a persistent cache using something like SQL server or file based storage…
https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#:~:text=MSAL%20caches%20a%20token%20after%20it%27s%20been%20acquired.,the%20session%20cookie%20that%27s%20in%20the%20browser%2C%20however.
However in this article we will focus on how to reject the authentication cookie.
Asp.Net
You can implement a Cookie Authentication event to check if the currently signed in user is in the MSAL token cache and if they are not, reject the cookie and force them to sign in again.
The following implementation does depend you have the following helpers:
And the Microsoft.Identity.Web.TokenCache nuget package installed.
app.UseCookieAuthentication(new CookieAuthenticationOptions { Provider = new CookieAuthenticationProvider() { OnValidateIdentity = async context => { // To Resolve: No account or login hint was passed to the AcquireTokenSilent call. IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication(); var signedInUserIdentity = new ClaimsPrincipal(context.Identity); if (await clientApp.GetAccountAsync(signedInUserIdentity.GetAccountId()) == null) { context.RejectIdentity(); } } } });
Asp.Net Core
First, create the custom Cookie Authentication event with the logic to check if the user can acquire a token successfully…
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; using Microsoft.Identity.Client; using System.Threading.Tasks; using System.Security.Claims; namespace SampleApp.Services { internal class RejectSessionCookieWhenAccountNotInCacheEvents : CookieAuthenticationEvents { public async override Task ValidatePrincipal(CookieValidatePrincipalContext context) { // Get the registered service that implements MSAL assuming you have one var msalInstance = context.HttpContext.RequestServices.GetRequiredService<IMsalService>(); // Get the MSAL IConfidentialClientApplication object IConfidentialClientApplication msalClient = msalInstance.GetClient(); var accounts = await msalClient.GetAccountsAsync(); // Get the signed in user account from MSAL if there is one var account = await msalClient.GetAccountAsync(accounts.FirstOrDefault()); // If no account then reject the authentication cookie if (account == null) { context.RejectPrincipal(); } await base.OnValidatePrincipal(context); } } }
Finally, register your Custom Cookie Authentication event…
// Register the Custom Cookie Authentication event Services.Configure<CookieAuthenticationOptions>(cookieScheme, options=>options.Events=new RejectSessionCookieWhenAccountNotInCacheEvents());
Microsoft Identity Web
A good solution is already implemented in Microsoft Identity Web and you simply need to implement the guidance here…
https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access
If the above article does not work, you can manually clear the authentication cookie.
First, create the custom Cookie Authentication event with the logic to check if the user can acquire a token successfully…
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; using Microsoft.Identity.Client; using Microsoft.Identity.Web; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace aad_ms_id_web_mvc_app.Services { internal class RejectSessionCookieWhenAccountNotInCacheEvents : CookieAuthenticationEvents { public async override Task ValidatePrincipal(CookieValidatePrincipalContext context) { try { var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); string token = await tokenAcquisition.GetAccessTokenForUserAsync( scopes: new[] { "profile" }, user: context.Principal); } catch (MicrosoftIdentityWebChallengeUserException ex) when (AccountDoesNotExistInTokenCache(ex)) { context.RejectPrincipal(); } } /// <summary> /// Is the exception thrown because there is no account in the token cache? /// </summary> /// <param name="ex">Exception thrown by <see cref="ITokenAcquisition"/>.GetTokenForXX methods.</param> /// <returns>A boolean telling if the exception was about not having an account in the cache</returns> private static bool AccountDoesNotExistInTokenCache(MicrosoftIdentityWebChallengeUserException ex) { return ex.InnerException is MsalUiRequiredException && (ex.InnerException as MsalUiRequiredException).ErrorCode == "user_null"; } } }
Finally, register the Custom Cookie Authentication event…
// Add Microsoft Identity Web services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(Configuration.GetSection("GraphBeta")) .AddInMemoryTokenCaches(); // Register the Custom Cookie Authentication event Services.Configure<CookieAuthenticationOptions>(cookieScheme, options=>options.Events=new RejectSessionCookieWhenAccountNotInCacheEvents());